/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.robotplayground.core.session;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executors;

import net.zarubsys.commons.utils.LoggingUncaughtExceptionHandler;
import net.zarubsys.robotplayground.core.device.IDevice;
import net.zarubsys.robotplayground.core.device.IDeviceConfiguration;
import net.zarubsys.robotplayground.core.modules.IModule;
import net.zarubsys.robotplayground.core.modules.IModuleConfiguration;
import net.zarubsys.robotplayground.core.monitors.IMonitor;
import net.zarubsys.robotplayground.core.program.API;
import net.zarubsys.robotplayground.core.program.IProgram;
import net.zarubsys.robotplayground.core.program.IProgramConfiguration;
import net.zarubsys.robotplayground.core.program.IProgramFinishedListener;
import net.zarubsys.robotplayground.core.program.IProgramFinishedListenerInternal;
import net.zarubsys.robotplayground.core.program.ProgramRunnable;

/**
 * RunningSession
 * 
 * Instance of this class represents a session ready to run, 
 * running or terminated.
 * 
 * Use only documented methods.
 *
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class RunningSession {
	
	private final API api;
	private final Collection<ProgramRunnable> programRunnables;
	private final List<IDevice<IDeviceConfiguration>> devices;
	private final List<IModule<? extends IDevice, ? extends IModuleConfiguration>> modules;
	private final List<IMonitor> monitors;
	private Session session;
	private boolean finished = false;
	
	private List<IProgramFinishedListener> programFinishedListeners = new ArrayList<>();
	
	private static final LoggingUncaughtExceptionHandler LOGGING_HANDLER = new LoggingUncaughtExceptionHandler();
	
	public RunningSession(API api, List<IDevice<IDeviceConfiguration>> devices, List<IModule<? extends IDevice, ? extends IModuleConfiguration>> modules, 
			List<IMonitor> monitors, Collection<ProgramRunnable> programRunnables, Session session) {
		this.api = api;
		this.devices = devices;
		this.modules = modules;
		this.monitors = monitors;
		this.programRunnables = programRunnables;
		this.session = session;
	}
	
	public API getApi() {
		return api;
	}
	
	public Collection<ProgramRunnable> getProgramRunnables() {
		return programRunnables;
	}

	/**
	 * @return true if the execution
	 * is finished
	 */
	public boolean isFinished() {
		return finished;
	}

	public void setFinished(boolean finished) {
		this.finished = finished;
	}

	public List<IDevice<IDeviceConfiguration>> getDevices() {
		return devices;
	}

	public List<IModule<? extends IDevice, ? extends IModuleConfiguration>> getModules() {
		return modules;
	}

	public List<IMonitor> getMonitors() {
		return monitors;
	}
	
	/**
	 * Use this method to add listener to the session termination
	 * @param listener an instance of the listener
	 */
	public void addProgramFinishedListener(IProgramFinishedListener listener) {
		programFinishedListeners.add(listener);
	}
	
	public void run() {
		SessionsHelper.getInstance().registerRunningSession(session, this);
		
		ProgramFinishedListener listener = new ProgramFinishedListener(programRunnables.size());
		for (ProgramRunnable programRunnable : programRunnables) {
			programRunnable.addFinishedListener(listener);
			Thread t = Executors.defaultThreadFactory().newThread(programRunnable);
			t.setName(programRunnable.getProgram().getId());
			t.setUncaughtExceptionHandler(LOGGING_HANDLER);
			t.start();
		}
	}
	
	/**
	 * @return an instance of session
	 * to which this instance is associated
	 */
	public Session getSession() {
		return session;
	}
	
	private class ProgramFinishedListener implements IProgramFinishedListenerInternal {
		
		private int programsCount;

		public ProgramFinishedListener(int programsCount) {
			this.programsCount = programsCount;
		}

		@Override
		public synchronized void notifyFinished(IProgram<IProgramConfiguration> program) {
			programsCount--;
			
			for (IProgramFinishedListener listener : programFinishedListeners) {
				listener.notifyFinished(program);
			}
			
			if (programsCount == 0) {
				setFinished(true);
				SessionsHelper.getInstance().removeRunningSession(getSession());
				
				for (IProgramFinishedListener listener : programFinishedListeners) {
					listener.notifyFinishedAll();
				}
			}
			
		}

	}
	
}

